﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Diagnostics;

namespace MySilverlightAlbum
{
    public partial class ImageViewer : UserControl
    {
        private const string                            ERR_LOAD_FMT = "Cannot load the selected image:{0}";
        private const string                            ERR_TITLE = "Error";
        private const int                               PROGRESS_WIDTH = 50;
        private const int                               PROGRESS_HEIGHT = 8;

        private const int                               ANIM_DURATION_MS = 500;
        private const double                            DEFAULT_ZOOM = 0.2;
        private const double                            WHEEL_DEVIDER = 300.0;

        private BitmapImage                             m_image;
        private double                                  m_percOffsetX = 0.5;
        private double                                  m_percOffsetY = 0.5;
        private Storyboard                              m_storyboard;
        private bool                                    m_initialized = false;
        private ProgressBarControl                      m_progress;

        public ImageViewer()
        {
            InitializeComponent();

            this.MouseWheel += OnWheel;
            this.MouseMove += OnMouseMove;
            this.MouseLeftButtonUp += OnLeftMouseUp;
        }

        #region Public Methods and Properties

        public void ZoomOut(
        )
        {
            ZoomImage(DEFAULT_ZOOM);
        }

        public void ZoomIn(
        )
        {
            ZoomImage(-DEFAULT_ZOOM);
        }

        public void Center(
        )
        {
            ZoomImage(0.0);
        }

        public string ImageUrl
        {
            get
            {
                if (m_image != null)
                {
                    return m_image.UriSource.ToString();
                }
                else
                {
                    return String.Empty;
                }
            }

            set
            {
                if (m_image != null)
                {
                    m_image.ImageOpened -= OnImageOpened;
                    m_image.ImageFailed -= OnImageFailed;

                    if (m_progress != null)
                    {
                        canvas.Children.Remove(m_progress);
                    }
                }

                m_initialized = false;
                m_image = null;

                if (!String.IsNullOrEmpty(value))
                {
                    m_progress = new ProgressBarControl();
                    m_progress.Height = PROGRESS_HEIGHT;
                    m_progress.Width = PROGRESS_WIDTH;
                    canvas.Children.Add(m_progress);

                    m_progress.SetValue(Canvas.LeftProperty, (canvas.ActualWidth + PROGRESS_WIDTH) / 2);
                    m_progress.SetValue(Canvas.TopProperty, (canvas.ActualHeight + PROGRESS_HEIGHT) / 2);

                    m_image = new BitmapImage(new Uri(value, UriKind.RelativeOrAbsolute));
                    m_image.ImageOpened += OnImageOpened;
                    m_image.ImageFailed += OnImageFailed;
                    m_image.DownloadProgress += OnDownloadProgress;
                }

                image.SetValue(Image.SourceProperty, m_image);
            }
        }

        void OnDownloadProgress(
            object                                      sender, 
            DownloadProgressEventArgs                   e
        )
        {
            Debug.Assert(m_progress != null);

            m_progress.Progress = e.Progress;
        }

        #endregion

        #region Private Methods and Properties

        void OnLeftMouseUp(
            object                                      sender, 
            MouseButtonEventArgs                        e
        )
        {
            UpdateMouseScrollPos(e);

            if (m_initialized)
            {
                if ((Keyboard.Modifiers & ModifierKeys.Alt) != 0)
                {
                    ZoomOut();
                }
                else
                {
                    ZoomIn();
                }
            }
        }

        private void UpdateMouseScrollPos(
            MouseEventArgs                              e
        )
        {
            Point                                       pos;

            if (m_initialized)
            {
                pos = e.GetPosition(this);
                
                m_percOffsetY = pos.Y / this.ActualHeight;
                m_percOffsetX = pos.X / this.ActualWidth;
            }
        }

        private void OnMouseMove(
            object                                      sender, 
            MouseEventArgs                              e
        )
        {
            UpdateMouseScrollPos(e);

            if (m_initialized)
            {
                ZoomImage(0);
            }
        }

        private void OnWheel(
            object                                      sender, 
            MouseWheelEventArgs                         e
        )
        {
            ZoomImage(e.Delta / WHEEL_DEVIDER);
        }

        private void ZoomImage(
            double                                      delta
        )
        {
            Storyboard                                  sb;
            double                                      newWidth;
            double                                      newHeight;
            double                                      top;
            double                                      left;
            double                                      newTop;
            double                                      newLeft;

            if (m_initialized)
            {
                if (m_storyboard != null)
                {
                    m_storyboard.SkipToFill();
                    m_storyboard = null;
                }

                sb = new Storyboard();
                sb.Duration = new Duration(new TimeSpan(0, 0, 0, 0, ANIM_DURATION_MS));
                
                newWidth = Math.Max(0, image.Width * (1 - delta));
                newHeight = Math.Max(0, image.Height * (1 - delta));
                
                top = (double) image.GetValue(Canvas.TopProperty);
                left = (double) image.GetValue(Canvas.LeftProperty);

                if (newWidth <= ActualWidth)
                {
                    m_percOffsetX = 0.5;
                }
                newLeft = (ActualWidth - newWidth) * m_percOffsetX;

                if (newHeight <= ActualHeight)
                {
                    m_percOffsetY = 0.5;
                    
                }
                newTop = (ActualHeight - newHeight) * m_percOffsetY;

                AddDoubleAnimation(sb, top, newTop, image, Canvas.TopProperty);
                AddDoubleAnimation(sb, left, newLeft, image, Canvas.LeftProperty);
                AddDoubleAnimation(sb, image.Width, newWidth, image,WidthProperty);
                AddDoubleAnimation(sb, image.Height, newHeight, image, HeightProperty);

                sb.Begin();
                m_storyboard = sb;
            }
        }

        private void AddDoubleAnimation(
            Storyboard                                  sb,
            double                                      from,
            double                                      to,
            FrameworkElement                            fa,
            DependencyProperty                          dp
        )
        {
            DoubleAnimation                             da;

            da = new DoubleAnimation();
            da.From = from;
            da.To = to;

            Storyboard.SetTarget(da, fa);
            Storyboard.SetTargetProperty(da, new PropertyPath(dp));
            sb.Children.Add(da);

        }

        private void OnImageOpened(
            object                                      sender, 
            RoutedEventArgs                             e
        )
        {
            image.Height = m_image.PixelHeight;
            image.Width = m_image.PixelWidth;
            
            canvas.Children.Remove(m_progress);

            image.SetValue(Canvas.TopProperty, (ActualHeight - image.Height) / 2);
            image.SetValue(Canvas.LeftProperty, (ActualWidth - image.Width) / 2);

            m_initialized = true;
        }

        private void OnImageFailed(
            object                                      sender, 
            ExceptionRoutedEventArgs                    e
        )
        {
            canvas.Children.Remove(m_progress);

            MessageBox.Show(
                String.Format(ERR_LOAD_FMT, ImageUrl), 
                ERR_TITLE,
                MessageBoxButton.OK);
        }


        #endregion
    }
}
